#ifndef __YUECHUAN_ADDRESS_H
#define __YUECHUAN_ADDRESS_H


#include <string>
#include <memory>
#include <sstream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "log.h"
#include <sys/un.h>
#include <netdb.h>
#include <ifaddrs.h>
#include "endian.hpp"

namespace yuechuan {
    class IPAddress;
    /**
     * @brief 地址基类
     * @details 这里仅仅是基类，由其派生出来的子类有 IPAddress，UnixAddress
     */
    class Address {
    public:
        typedef std::shared_ptr<Address> ptr;


        /**
         * @brief 虚析构函数
         * @details 因为其是基类，对于派生类继承该类，需要将基类的析构函数设置为虚函数
         *          这样在删除一个指向派生类的基类指针时，会自动调用派生类的析构函数
         *          从而正确的释放派生类对象的内存空间
         */
        virtual  ~Address() {};

        /**
         * @brief 将任何地址类型转换为自定义的类型
         * @details 这里可以针对ipv6和ipv4两个不同的协议族类型进行转换
         * @param addr 地址
         * @param addrLen 地址数据长度
         * @return IPAddress::ptr 指向派生类的智能指针
         */
        static Address::ptr Create(const sockaddr *addr, socklen_t addrLen);

        /**
         * @brief 将域名转换为地址
         * @details struct addrinfo{
         *               int                ai_flags;       用于控制getaddrinfo()函数的行为
         *               int                ai_family;      地址族
         *               int                ai_socktype;    套接字类型如SOCK_STREM和SOCK_DGRAM
         *               int                ai_protocol;    协议类型如IPPROTO_TCP和IPPROTP_UDP
         *               socklen_t          ai_addrlen;     地址长度
         *               struct sockaddr   *ai_addr;        地址信息
         *               char              *ai_canonname;   主机名称
         *               struct  addrinfo  *ai_next;        下一个地址信息
         *               };
         *
         * @param result 转换后的地址结果
         * @param host 域名
         * @param family 协议族
         * @param type套接字类型
         * @param protocol 协议类型
         * @return 是否转换成功
         */
        static bool Lookup(std::vector<Address::ptr> &result, const std::string &host,
                           int family = AF_INET, int type = 0, int protocol = 0);

        static Address::ptr LookupAny(const std::string &host,
                                      int family = AF_INET, int type = 0, int protocol = 0);

        static std::shared_ptr<IPAddress> LookupAnyIPAddress(const std::string &host,
                                          int family = AF_INET, int type = 0, int protocol = 0);

        /**
         * @brief 从网卡信息获取系统上可用的网络接口信息
         * @details 使用getifaddrs函数
         *           struct ifaddrs{
         *              struct ifaddrs *ifa_next;           // 指向下一个接口
         *              char           *ifa_name;           // 接口名
         *              unsigned int   ifa_flags;           // 接口标志
         *              struct sockaddr*ifa_addr;           // 接口地址
         *              struct sockaddr*ifa_netmask;        // 接口子网掩码
         *              union{
         *                  struct sockaddr*  ifu_broadaddr;// 接口广播地址
         *                  struct sockaddr*  ifu_dstaddr;  // 网络设备的目标地址
         *                  }ifa_ifu;
         *              void            *ifa_data;           // 接口私有数据
         *            };
         * @param result 网络接口名称: {网络接口的地址，前缀的长度} 组成的字典
         * @param family 协议族
         * @return 是否获取成功
         */
        static bool GetInterfaceAddress(std::multimap<std::string, std::pair<Address::ptr, uint32_t> > &result,
                                        int family = AF_INET);

        /**
         * @brief 从网卡信息获取系统上可用的网络接口信息
         * @details 使用getifaddrs函数
         *           struct ifaddrs{
         *              struct ifaddrs *ifa_next;           // 指向下一个接口
         *              char           *ifa_name;           // 接口名
         *              unsigned int   ifa_flags;           // 接口标志
         *              struct sockaddr*ifa_addr;           // 接口地址
         *              struct sockaddr*ifa_netmask;        // 接口子网掩码
         *              union{
         *                  struct sockaddr*  ifu_broadaddr;// 接口广播地址
         *                  struct sockaddr*  ifu_dstaddr;  // 网络设备的目标地址
         *                  }ifa_ifu;
         *              void            *ifa_data;           // 接口私有数据
         *            };
         * @param result {网络接口地址，前缀长度}组成的数组
         * @param family 协议族
         * @return 是否获取成功
         */
        static bool GetInterfaceAddresses(std::vector<std::pair<Address::ptr, uint32_t> > &result, std::string &iface,
                                          int family = AF_INET);

        /**
         * @brief 获取协议族信息
         * @return int 地址的协议族
         */
        int getFamily() const;

        /**
         * @brief 获取地址
         * @return sockaddr
         */
        virtual const sockaddr *getAddress() const = 0;


        virtual sockaddr *getAddress() = 0;

        /**
         * @brief 获取地址长度
         * @return socklen_t
         */
        virtual  socklen_t getAddrLen() const = 0;

        /**
         * @brief 将地址信息输入的输出流中
         * @param os 输出控件流
         * @return
         */
        virtual std::ostream &insert(std::ostream &os) const = 0;

        /**
         * @brief 将地址信息转换为字符串
         * @details 在其派生类中都实现了insert方法，直接调用insert方法即可
         * @return
         */
        std::string toString() const;

        /**
         * @brief 重写address的比较函数
         * @details 先获取两个地址中的较小的地址长度minLen
         *          然后再将两个地址的前minLen进行比较
         *          较小的放在前面
         *          当两个地址的前minLen的地址相同，则比较两个地址的长度
         *          长度小的放在前面
         *          当长度也相同时，说明两个地址相同
         * @param rhs 要进行比较的对象
         * @return bool
         */
        bool operator<(const Address &rhs) const;

        /**
         * @brief 重写Address的相等比较函数
         * @param rhs 要进行比较的对象
         * @return bool
         */
        bool operator==(const Address &rhs) const;

        /**
         * @brief 重写不等比较函数
         * @details 直接使用上面重写的==比较运算符
         * @param rhs 要进行比较的对象
         * @return
         */
        bool operator!=(const Address &rhs) const;
    };


    /**
     * @brief 虚拟基类，IP地址
     * @details 由其派生出来的子类有IPV4Address 和 IPV6Address
     */
    class IPAddress : public Address {
    public:
        typedef std::shared_ptr<IPAddress> ptr;

        /**
         * @brief 将char*地址转换为地址类型
         * @details 针对ipv4和ipv6不同处理
         * @param address 字符串地址
         * @param port 端口号
         * @return IPAddress::ptr 创建的地址对象指针
         */
        static IPAddress::ptr Create(const char *address, const uint16_t port = 0);

        /**
         * @brief 获取IP地址的网关地址
         * @param prefix_len 前缀长度
         * @return IPAddress::ptr
         */
        virtual IPAddress::ptr broadcastAddress(uint32_t prefix_len) = 0;

        /**
         * @brief 获取IP地址的网络地址
         * @param prefix_len 前缀长度
         * @return IPAddress::ptr
         */
        virtual IPAddress::ptr networdAddress(uint32_t prefix_len) = 0;

        /**
         * @brief 获取IP地址的子网掩码
         * @param prefix_len 前缀长度
         * @return IPAddress::ptr
         */
        virtual IPAddress::ptr subnetMask(uint32_t prefix_len) = 0;

        /**
         * @brief 获取IP地址的端口号
         * @return uint32_t
         */
        virtual uint16_t getPort() const = 0;

        /**
         * @brief 设置IP地址的端口号
         * @param p 端口号
         */
        virtual void setPort(uint16_t p) = 0;
    };

    /**
     * @brief IPV4地址对象
     * @inherit IPAddress
     */
    class IPV4Address : public IPAddress {
    public:
        typedef std::shared_ptr<IPV4Address> ptr;

        /**
         * @@brief 含参构造函数
         * @details 将m_addr赋值为addr
         * @param addr
         */
        IPV4Address(const sockaddr_in &addr);

        /**
         * @brief 含参构造函数
         * @param address IPV4绑定的地址，默认为INADDR_ANY，0.0.0.0,接收任何传入的连接
         * @param port IPV4绑定的端口号
         */
        IPV4Address(uint32_t address = INADDR_ANY, uint16_t port = 0);


        /**
         * @brief 将文本类型地址转换为定义的地址类型
         * @param address 字符串类型的地址
         * @param port 端口号
         * @return IPV4Address::ptr 根据传入的地址创建的对象指针
         */
        static IPV4Address::ptr Create(const char *address, uint16_t port = 0);

        /**
        * @brief 获取地址信息
        * @return
        */
        const sockaddr *getAddress() const override;

        sockaddr *getAddress() override;

        /**
         * @brief  获取地址长度
         * @return socklen_t sizeof(m_addr）
         */
        socklen_t getAddrLen() const override;

        /**
         * @brief 打印输出字段信息
         * @param os 输出控制流
         * @return
         */
        std::ostream &insert(std::ostream &os) const override;

        /**
         * @brief 获取广播地址对象
         * @param prefix_len 前缀长度
         * @return IPV4Address::prt 创建的广播地址对象指针
         */
        IPAddress::ptr broadcastAddress(uint32_t prefix_len) override;

        /**
         * @brief 获取网络地址对象
         * @param prefix_len 前缀长度
         * @return IPV4Address::ptr 创建的网络地址对象指针
         */
        IPAddress::ptr networdAddress(uint32_t prefix_len) override;

        /**
         * @brief 获取子网掩码的地址对象
         * @param prefix_len 前缀长度
         * @return
         */
        IPAddress::ptr subnetMask(uint32_t prefix_len) override;

        /**
         * @brief 获取端口号
         * @return
         */
        uint16_t getPort() const override;

        /**
         * @brief 设置端口号
         * @param p 端口号
         */
        void setPort(uint16_t p) override;

    private:
        /// ipv4套接字地址
        sockaddr_in m_addr;

    };

    /**
     * @brief IPV6地址对象
     * @inherit IPAddress
     */
    class IPV6Address : public IPAddress {
    public:
        typedef std::shared_ptr<IPV6Address> ptr;

        IPV6Address();

        /**
         * @brief 含参构造函数
         * @details 将传入的ipv6的地址直接赋值给m_addr
         * @param addr ipv6的地址
         */
        IPV6Address(const sockaddr_in6 &addr);

        /**
         * @brief 含参构造函数
         * @param addr 16字节长度的地址
         * @param port 端口号
         */
        IPV6Address(const uint8_t address[16], uint16_t port = 0);

        /**
       * @brief 将字符串地址转换为自定义的地址对象指针
       * @param address IPV6绑定的地址，默认为INADDR_ANY
       * @param port IPV6绑定的端口号
       */
        static IPV6Address::ptr Create(const char *address, uint16_t port = 0);

        /**
         * @brief 获取地址信息
         * @return sockaddr*
         */
        const sockaddr *getAddress() const override;

        sockaddr* getAddress() override;
        /**
         * @brief 获取地址长度
         * @return socklen_t
         */
        socklen_t getAddrLen() const override;

        /**
         * @brief 将地址信息输入到输出流中
         * @param os 输出控制流
         * @return
         */
        std::ostream &insert(std::ostream &os) const override;

        /**
         * @brief 广播地址对象
         * @param prefix_len 前缀长度
         * @return IPV6Address::ptr 创建的广播地址的智能指针
         */
        IPAddress::ptr broadcastAddress(uint32_t prefix_len) override;

        /**
         * @brief 网络字段地址
         * @param prefix_len 前缀长度
         * @return IPAddress::ptr 创建的网络地址的智能指针
         */
        IPAddress::ptr networdAddress(uint32_t prefix_len) override;

        /**
         * @brief 子网掩码地址
         * @param prefix_len 前缀长度
         * @return IPAddress::ptr 创建的子网掩码地址的智能指针
         */
        IPAddress::ptr subnetMask(uint32_t prefix_len) override;

        /**
         * @brief 获取端口号
         * @return uint32_t
         */
        uint16_t getPort() const override;

        /**
         * @brief 设置端口号
         * @param p 端口号
         */
        void setPort(uint16_t p) override;

    private:
        /// ipv6套接字地址
        sockaddr_in6 m_addr;

    };

    class UnixAddress : public Address {
    public:
        typedef std::shared_ptr<UnixAddress> ptr;

        /**
         * @brief 无参构造函数
         */
        UnixAddress();

        /**
         * @brief 含参构造函数
         * @param path unix域套接字的路径名称 通常为108个字符
         */
        UnixAddress(const std::string &path);

        /**
         * @brief 获取地址信息
         * @return const sockaddr*
         */
        const sockaddr *getAddress() const override;

        /**
         * @brief 获取地址信息
         * @return sockaddr*
         */
        sockaddr* getAddress() override;

        /**
         * @brief 获取地址长度
         * @return socklen_t
         */
        socklen_t getAddrLen() const override;

        /**
         * @brief 设置地址长度
         * @param length
         */
        void setAddrLen(uint32_t length);
        /**
         * @brief 将地址信息输入到输出流中
         * @param os 输出控制流
         * @return
         */
        std::ostream &insert(std::ostream &os) const override;

    private:
        /// 地址
        struct sockaddr_un m_addr;
        /// 地址长度
        socklen_t m_length;
    };

    /**
     * @brief 未知的地址对象
     */
    class UnknowAddress : public Address {
    public:
        std::shared_ptr<UnknowAddress> ptr;

        /**
         * @brief 含参构造函数
         * @details 直接将addr的值赋值到m_addr中
         * @param addr 地址结构体
         */
        UnknowAddress(const sockaddr &addr);

        /**
         * @brief 含参构造函数
         * @details 设置地址对象的协议族
         * @param family 协议族
         */
        UnknowAddress(int family);

        /**
         * @brief 获取地址信息
         * @return sockaddr*
         */
        const sockaddr *getAddress() const override;

        sockaddr* getAddress() override;
        /**
         * @brief 获取地址长度
         * @return socklen_t
         */
        socklen_t getAddrLen() const override;


        /**
         * @brief 将地址信息输入到输出流中
         * @param os 输出控制流
         * @return
         */
        std::ostream &insert(std::ostream &os) const override;

    private:
        /// 地址
        sockaddr m_addr;

    };
}// namespace yuechuan


#endif //YUECHUAN_ADDRESS_H
